/*==============================================================================

  Program: 3D Slicer

  Copyright (c) Laboratory for Percutaneous Surgery (PerkLab)
  Queen's University, Kingston, ON, Canada. All Rights Reserved.

  See COPYRIGHT.txt
  or http://www.slicer.org/copyright/copyright.txt for details.

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

  This file was originally developed by Csaba Pinter, PerkLab, Queen's University
  and was supported through the Applied Cancer Research Unit program of Cancer Care
  Ontario with funds provided by the Ontario Ministry of Health and Long-Term Care

==============================================================================*/

// Terminologies includes
#include "qSlicerTerminologyNavigatorWidget.h"

#include "ui_qSlicerTerminologyNavigatorWidget.h"

#include "vtkSlicerTerminologiesModuleLogic.h"

// Slicer includes
#include <qSlicerApplication.h>
#include <qSlicerModuleManager.h>
#include <qSlicerAbstractCoreModule.h>

// VTK includes
#include <vtkSmartPointer.h>

// Qt includes
#include <QColor>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QItemSelection>
#include <QMessageBox>
#include <QSettings>
#include <QTableWidgetItem>
#include <QTimer>

//-----------------------------------------------------------------------------
// TerminologyInfoBundle methods

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle::TerminologyInfoBundle()
  {
  this->TerminologyEntry = vtkSlicerTerminologyEntry::New();
  }

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle::TerminologyInfoBundle(
  vtkSlicerTerminologyEntry* entry, QString name, bool nameAutoGenerated, QColor color, bool colorAutoGenerated, QColor generatedColor )
  : Name(name)
  , NameAutoGenerated(nameAutoGenerated)
  , Color(color)
  , ColorAutoGenerated(colorAutoGenerated)
  , GeneratedColor(generatedColor)
  {
  this->TerminologyEntry = vtkSlicerTerminologyEntry::New();
  if (entry)
    {
    this->TerminologyEntry->Copy(entry);
    }
  }

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle::~TerminologyInfoBundle()
  {
  if (this->TerminologyEntry)
    {
    this->TerminologyEntry->Delete();
    this->TerminologyEntry = nullptr;
    }
  }

//-----------------------------------------------------------------------------
const qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle&
  qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle::operator=(
    const qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle& other )
  {
  this->TerminologyEntry->Copy(other.TerminologyEntry);
  this->Name = other.Name;
  this->NameAutoGenerated = other.NameAutoGenerated;
  this->Color = other.Color;
  this->ColorAutoGenerated = other.ColorAutoGenerated;
  this->GeneratedColor = other.GeneratedColor;
  return *this;
  }

//-----------------------------------------------------------------------------
vtkSlicerTerminologyEntry* qSlicerTerminologyNavigatorWidget::TerminologyInfoBundle::GetTerminologyEntry()
{
  return this->TerminologyEntry;
}

//-----------------------------------------------------------------------------
// qSlicerTerminologyNavigatorWidgetPrivate methods

//-----------------------------------------------------------------------------
class qSlicerTerminologyNavigatorWidgetPrivate: public Ui_qSlicerTerminologyNavigatorWidget
{
  Q_DECLARE_PUBLIC(qSlicerTerminologyNavigatorWidget);

protected:
  qSlicerTerminologyNavigatorWidget* const q_ptr;
public:
  qSlicerTerminologyNavigatorWidgetPrivate(qSlicerTerminologyNavigatorWidget& object);
  ~qSlicerTerminologyNavigatorWidgetPrivate();
  void init();

  /// Get terminology module logic
  static vtkSlicerTerminologiesModuleLogic* terminologyLogic();

  /// Reset current category name and container object
  void resetCurrentCategory();
  /// Reset current type name and container object
  void resetCurrentType();
  /// Reset current type modifier name and container object
  void resetCurrentTypeModifier();

  // Set name from current selection and set it to name text box
  void setNameFromCurrentTerminology();
  // Set recommended color from current selection to color picker
  void setRecommendedColorFromCurrentTerminology();

  /// Reset current region name and container object
  void resetCurrentRegion();
  /// Reset current region modifier name and container object
  void resetCurrentRegionModifier();

  /// Find item in category table widget corresponding to a given category
  QTableWidgetItem* findTableWidgetItemForCategory(vtkSlicerTerminologyCategory* category);
  /// Find item in (type or region) table widget corresponding to a given type
  QTableWidgetItem* findTableWidgetItemForType(QTableWidget* tableWidget, vtkSlicerTerminologyType* type);
  /// Find index in (type or region) modifier combobox corresponding to a given modifier
  /// \return -1 if not found
  int findComboBoxIndexForModifier(ctkComboBox* comboBox, vtkSlicerTerminologyType* modifier);

public:
  /// Name (SegmentationCategoryTypeContextName) of the current terminology
  QString CurrentTerminologyName;

  /// Object containing the details of the current category.
  /// This is the category containing the current type. It does not reflect UI selection
  vtkSlicerTerminologyCategory* CurrentCategoryObject;
  /// Category objects of the currently selected categories in the UI
  /// Used for populating the type list when categories are selected
  QList<vtkSmartPointer<vtkSlicerTerminologyCategory> > SelectedCategoryObjects;
  /// Object containing the details of the current type
  vtkSlicerTerminologyType* CurrentTypeObject;
  /// Object containing the details of the current type modifier if any
  vtkSlicerTerminologyType* CurrentTypeModifierObject;

  /// Name (AnatomicContextName) of the current anatomic context
  QString CurrentAnatomicContextName;

  /// Object containing the details of the current region
  vtkSlicerTerminologyType* CurrentRegionObject;
  /// Object containing the details of the current region modifier if any
  vtkSlicerTerminologyType* CurrentRegionModifierObject;

  /// Name of the "no selected type" item
  QString NoneItemName;

  /// Flag indicating whether name was automatically generated
  bool NameAutoGenerated;
  /// Flag indicating whether color is the recommended color from the selected terminology
  bool ColorAutoGenerated;
  /// Generated color that is used if there was no recommended color in selected terminology
  QColor GeneratedColor;

  /// Flag indicating whether terminology combobox is being populated.
  /// Used to omit certain operations when terminology selection is made when populating
  bool TerminologyComboboxPopulating;
  /// Flag indicating whether anatomic context combobox is being populated.
  /// Used to omit certain operations when anatomic context selection is made when populating
  bool AnatomicContextComboboxPopulating;
};

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidgetPrivate::qSlicerTerminologyNavigatorWidgetPrivate(qSlicerTerminologyNavigatorWidget& object)
  : q_ptr(&object)
  , NoneItemName(QString("[ None ]"))
  , NameAutoGenerated(true)
  , ColorAutoGenerated(true)
  , GeneratedColor(vtkSlicerTerminologyType::INVALID_COLOR[0], vtkSlicerTerminologyType::INVALID_COLOR[1], vtkSlicerTerminologyType::INVALID_COLOR[2])
  , TerminologyComboboxPopulating(false)
  , AnatomicContextComboboxPopulating(false)
{
  this->CurrentCategoryObject = vtkSlicerTerminologyCategory::New();
  this->CurrentTypeObject = vtkSlicerTerminologyType::New();
  this->CurrentTypeModifierObject = vtkSlicerTerminologyType::New();

  this->CurrentRegionObject = vtkSlicerTerminologyType::New();
  this->CurrentRegionModifierObject = vtkSlicerTerminologyType::New();
}

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidgetPrivate::~qSlicerTerminologyNavigatorWidgetPrivate()
{
  if (this->CurrentCategoryObject)
    {
    this->CurrentCategoryObject->Delete();
    this->CurrentCategoryObject = nullptr;
    }
  if (this->CurrentTypeObject)
    {
    this->CurrentTypeObject->Delete();
    this->CurrentTypeObject = nullptr;
    }
  if (this->CurrentTypeModifierObject)
    {
    this->CurrentTypeModifierObject->Delete();
    this->CurrentTypeModifierObject = nullptr;
    }

  if (this->CurrentRegionObject)
    {
    this->CurrentRegionObject->Delete();
    this->CurrentRegionObject = nullptr;
    }
  if (this->CurrentRegionModifierObject)
    {
    this->CurrentRegionModifierObject->Delete();
    this->CurrentRegionModifierObject = nullptr;
    }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::init()
{
  Q_Q(qSlicerTerminologyNavigatorWidget);
  this->setupUi(q);

  // Make connections
  QObject::connect(this->ComboBox_Terminology, SIGNAL(currentIndexChanged(int)),
    q, SLOT(onTerminologySelectionChanged(int)) );
  QObject::connect(this->tableWidget_Category, SIGNAL(itemSelectionChanged()),
    q, SLOT(onCategorySelectionChanged()) );
  QObject::connect(this->tableWidget_Type, SIGNAL(currentItemChanged(QTableWidgetItem*,QTableWidgetItem*)),
    q, SLOT(onTypeSelected(QTableWidgetItem*,QTableWidgetItem*)) );
  QObject::connect(this->ComboBox_TypeModifier, SIGNAL(currentIndexChanged(int)),
    q, SLOT(onTypeModifierSelectionChanged(int)) );
  QObject::connect(this->SearchBox_Category, SIGNAL(textChanged(QString)),
    q, SLOT(onCategorySearchTextChanged(QString)) );
  QObject::connect(this->SearchBox_Type, SIGNAL(textChanged(QString)),
    q, SLOT(onTypeSearchTextChanged(QString)) );

  QObject::connect(this->ComboBox_AnatomicContext, SIGNAL(currentIndexChanged(int)),
    q, SLOT(onAnatomicContextSelectionChanged(int)) );
  QObject::connect(this->tableWidget_AnatomicRegion, SIGNAL(currentItemChanged(QTableWidgetItem*,QTableWidgetItem*)),
    q, SLOT(onRegionSelected(QTableWidgetItem*,QTableWidgetItem*)) );
  QObject::connect(this->ComboBox_AnatomicRegionModifier, SIGNAL(currentIndexChanged(int)),
    q, SLOT(onRegionModifierSelectionChanged(int)) );
  QObject::connect(this->SearchBox_AnatomicRegion, SIGNAL(textChanged(QString)),
    q, SLOT(onRegionSearchTextChanged(QString)) );

  QObject::connect(this->lineEdit_Name, SIGNAL(textChanged(QString)),
    q, SLOT(onNameChanged(QString)) );
  QObject::connect(this->pushButton_ResetName, SIGNAL(clicked()),
    q, SLOT(onResetNameClicked()) );
  QObject::connect(this->ColorPickerButton_RecommendedRGB, SIGNAL(colorChanged(QColor)),
    q, SLOT(onColorChanged(QColor)) );
  QObject::connect(this->pushButton_ResetColor, SIGNAL(clicked()),
    q, SLOT(onResetColorClicked()) );

  // Set default settings for widgets
  this->tableWidget_Category->setEnabled(false);
  this->SearchBox_Category->setEnabled(false);
  this->tableWidget_Type->setEnabled(true); // None item always present
  this->SearchBox_Type->setEnabled(false);
  this->ComboBox_TypeModifier->setEnabled(false);

  this->SearchBox_AnatomicRegion->setEnabled(false);
  this->tableWidget_AnatomicRegion->setEnabled(false);
  this->ComboBox_AnatomicRegionModifier->setEnabled(false);

  // Apply initial state of expand buttons
  this->AnatomicalRegionExpandButton->setChecked(false);
  this->CategoryExpandButton->setChecked(false);

  // Set reset button sizes
  this->pushButton_ResetName->setMaximumHeight(this->lineEdit_Name->sizeHint().height());
  this->pushButton_ResetName->setMaximumWidth(this->lineEdit_Name->sizeHint().height());
  this->pushButton_ResetColor->setMaximumHeight(this->lineEdit_Name->sizeHint().height());
  this->pushButton_ResetColor->setMaximumWidth(this->lineEdit_Name->sizeHint().height());

  // Use the CTK color picker
  ctkColorPickerButton::ColorDialogOptions options = ctkColorPickerButton::UseCTKColorDialog;
  this->ColorPickerButton_RecommendedRGB->setDialogOptions(options);

  // Setup load buttons
  QObject::connect(this->pushButton_LoadTerminology, SIGNAL(clicked()),
    q, SLOT(onLoadTerminologyClicked()) );
  QObject::connect(this->pushButton_LoadAnatomicContext, SIGNAL(clicked()),
    q, SLOT(onLoadAnatomicContextClicked()) );

  // Populate terminology combobox with the loaded terminologies
  q->populateTerminologyComboBox();
  // Populate anatomic context combobox with the loaded anatomic contexts
  q->populateAnatomicContextComboBox();
}

//-----------------------------------------------------------------------------
vtkSlicerTerminologiesModuleLogic* qSlicerTerminologyNavigatorWidgetPrivate::terminologyLogic()
{
  if (!qSlicerCoreApplication::application()
    || !qSlicerCoreApplication::application()->moduleManager())
    {
    qCritical() << Q_FUNC_INFO << ": Module manager is not found";
    return nullptr;
    }
  qSlicerAbstractCoreModule* terminologiesModule = qSlicerCoreApplication::application()->moduleManager()->module("Terminologies");
  if (!terminologiesModule)
    {
    return nullptr; // No error log because it makes test fail
    }
  vtkSlicerTerminologiesModuleLogic* terminologyLogic =
    vtkSlicerTerminologiesModuleLogic::SafeDownCast(terminologiesModule->logic());
  if (!terminologyLogic)
    {
    qCritical() << Q_FUNC_INFO << ": Terminologies module logic is invalid";
    }
  return terminologyLogic;
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::resetCurrentCategory()
{
  if (this->CurrentCategoryObject)
    {
    this->CurrentCategoryObject->Delete();
    this->CurrentCategoryObject = nullptr;
    }
  this->CurrentCategoryObject = vtkSlicerTerminologyCategory::New();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::resetCurrentType()
{
  if (this->CurrentTypeObject)
    {
    this->CurrentTypeObject->Delete();
    this->CurrentTypeObject = nullptr;
    }
  this->CurrentTypeObject = vtkSlicerTerminologyType::New();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::resetCurrentTypeModifier()
{
  if (this->CurrentTypeModifierObject)
    {
    this->CurrentTypeModifierObject->Delete();
    this->CurrentTypeModifierObject = nullptr;
    }
  this->CurrentTypeModifierObject = vtkSlicerTerminologyType::New();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::setNameFromCurrentTerminology()
{
  Q_Q(qSlicerTerminologyNavigatorWidget);
  QString name = q->nameFromCurrentTerminology();
  if (name.isEmpty())
    {
    return;
    }

  // Set to auto-generated
  this->NameAutoGenerated = true;

  // Disable reset name button
  this->pushButton_ResetName->setEnabled(false);

  this->lineEdit_Name->blockSignals(true); // The callback function is to save the user's manual name entry
  this->lineEdit_Name->setText(name);
  this->lineEdit_Name->blockSignals(false);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::setRecommendedColorFromCurrentTerminology()
{
  // Set 'invalid' gray color if type is not selected or the selected type has modifiers but no modifier is selected
  unsigned char r = vtkSlicerTerminologyType::INVALID_COLOR[0];
  unsigned char g = vtkSlicerTerminologyType::INVALID_COLOR[1];
  unsigned char b = vtkSlicerTerminologyType::INVALID_COLOR[2];
  if ( !this->CurrentTypeObject ||
       (this->CurrentTypeObject->GetHasModifiers() && !this->CurrentTypeModifierObject) )
    {
    this->ColorPickerButton_RecommendedRGB->blockSignals(true); // The callback function is to save the user's custom color selection
    this->ColorPickerButton_RecommendedRGB->setColor(QColor(r,g,b));
    this->ColorPickerButton_RecommendedRGB->blockSignals(false);
    return;
    }

  // Get recommended color from terminology entry.
  // If the current type has no modifiers then set color form the type
  if (!this->CurrentTypeObject->GetHasModifiers())
    {
    this->CurrentTypeObject->GetRecommendedDisplayRGBValue(r,g,b);
    }
  else
    {
    this->CurrentTypeModifierObject->GetRecommendedDisplayRGBValue(r,g,b);
    }

  // Use generated color if recommended color is missing (i.e. the default invalid color)
  if ( r == vtkSlicerTerminologyType::INVALID_COLOR[0]
    && g == vtkSlicerTerminologyType::INVALID_COLOR[1]
    && b == vtkSlicerTerminologyType::INVALID_COLOR[2] )
    {
    r = this->GeneratedColor.red();
    g = this->GeneratedColor.green();
    b = this->GeneratedColor.blue();
    }

  // Set to auto-generated
  this->ColorAutoGenerated = true;

  // Disable reset color button
  this->pushButton_ResetColor->setEnabled(false);

  this->ColorPickerButton_RecommendedRGB->blockSignals(true); // The callback function is to save the user's custom color selection
  this->ColorPickerButton_RecommendedRGB->setColor(QColor(r,g,b));
  this->ColorPickerButton_RecommendedRGB->blockSignals(false);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::resetCurrentRegion()
{
  if (this->CurrentRegionObject)
    {
    this->CurrentRegionObject->Delete();
    this->CurrentRegionObject = nullptr;
    }
  this->CurrentRegionObject = vtkSlicerTerminologyType::New();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidgetPrivate::resetCurrentRegionModifier()
{
  if (this->CurrentRegionModifierObject)
    {
    this->CurrentRegionModifierObject->Delete();
    this->CurrentRegionModifierObject = nullptr;
    }
  this->CurrentRegionModifierObject = vtkSlicerTerminologyType::New();
}

//-----------------------------------------------------------------------------
QTableWidgetItem* qSlicerTerminologyNavigatorWidgetPrivate::findTableWidgetItemForCategory(vtkSlicerTerminologyCategory* category)
{
  if (!category)
    {
    return nullptr;
    }

  QString categoryName(category->GetCodeMeaning());
  Qt::MatchFlags flags = Qt::MatchExactly | Qt::MatchCaseSensitive;
  QList<QTableWidgetItem*> items = this->tableWidget_Category->findItems(categoryName, flags);
  if (items.count() == 0)
    {
    return nullptr;
    }

  foreach (QTableWidgetItem* item, items)
    {
    QString codingSchemeDesignator = item->data(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole).toString();
    QString codeValue = item->data(qSlicerTerminologyNavigatorWidget::CodeValueRole).toString();
    if ( category->GetCodingSchemeDesignator() && !codingSchemeDesignator.compare(category->GetCodingSchemeDesignator())
      && category->GetCodeValue() && !codeValue.compare(category->GetCodeValue()) )
      {
      return item;
      }
    }

    return nullptr;
}

//-----------------------------------------------------------------------------
QTableWidgetItem* qSlicerTerminologyNavigatorWidgetPrivate::findTableWidgetItemForType(QTableWidget* tableWidget, vtkSlicerTerminologyType* type)
{
  if (!tableWidget || !type)
    {
    return nullptr;
    }

  QString typeName(type->GetCodeMeaning());
  Qt::MatchFlags flags = Qt::MatchExactly | Qt::MatchCaseSensitive;
  QList<QTableWidgetItem*> items = tableWidget->findItems(typeName, flags);
  if (items.count() == 0)
    {
    return nullptr;
    }

  foreach (QTableWidgetItem* item, items)
    {
    QString codingSchemeDesignator = item->data(qSlicerTerminologyNavigatorWidget::CodingSchemeDesignatorRole).toString();
    QString codeValue = item->data(qSlicerTerminologyNavigatorWidget::CodeValueRole).toString();
    if ( type->GetCodingSchemeDesignator() && !codingSchemeDesignator.compare(type->GetCodingSchemeDesignator())
      && type->GetCodeValue() && !codeValue.compare(type->GetCodeValue()) )
      {
      return item;
      }
    }

    return nullptr;
}

//-----------------------------------------------------------------------------
int qSlicerTerminologyNavigatorWidgetPrivate::findComboBoxIndexForModifier(ctkComboBox* comboBox, vtkSlicerTerminologyType* modifier)
{
  if (!comboBox || !modifier)
    {
    return -1;
    }

  QString modifierName(modifier->GetCodeMeaning());
  int modifierIndex = comboBox->findText(modifierName);
  return modifierIndex;
}

//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
// qSlicerTerminologyNavigatorWidget methods

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidget::qSlicerTerminologyNavigatorWidget(QWidget* _parent)
  : qMRMLWidget(_parent)
  , d_ptr(new qSlicerTerminologyNavigatorWidgetPrivate(*this))
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->init();

  // Connect logic modified event (cannot call QVTK from private implementation)
  vtkSlicerTerminologiesModuleLogic* logic = qSlicerTerminologyNavigatorWidgetPrivate::terminologyLogic();
  if (logic)
    {
    qvtkConnect( logic, vtkCommand::ModifiedEvent, this, SLOT(onLogicModified()) );
    }
}

//-----------------------------------------------------------------------------
qSlicerTerminologyNavigatorWidget::~qSlicerTerminologyNavigatorWidget()
= default;

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::terminologyInfo(TerminologyInfoBundle &terminologyInfo)
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  this->terminologyEntry(terminologyInfo.GetTerminologyEntry());
  terminologyInfo.Name = d->lineEdit_Name->text();
  terminologyInfo.NameAutoGenerated = d->NameAutoGenerated;
  terminologyInfo.Color = d->ColorPickerButton_RecommendedRGB->color();
  terminologyInfo.ColorAutoGenerated = d->ColorAutoGenerated;
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::setTerminologyInfo(TerminologyInfoBundle &terminologyInfo)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Set terminology entry
  if (!this->setTerminologyEntry(terminologyInfo.GetTerminologyEntry()))
    {
    qWarning() << Q_FUNC_INFO << ": Failed to set terminology entry from given terminology info bundle";
    }
  bool noneType = (d->CurrentTypeObject->GetCodeValue() == nullptr);

  // Set name
  if (terminologyInfo.Name.isEmpty())
    {
    // If given name is empty, then generate it from terminology (auto-generate flag will be true)
    d->setNameFromCurrentTerminology();
    }
  else
    {
    d->lineEdit_Name->blockSignals(true); // Only call callback function if user changes from UI
    d->lineEdit_Name->setText(terminologyInfo.Name);
    d->lineEdit_Name->blockSignals(false);

    d->NameAutoGenerated = terminologyInfo.NameAutoGenerated;
    d->pushButton_ResetName->setEnabled(!d->NameAutoGenerated && !noneType);
    }

  // Store generated color. It is used when the selected terminology contains no recommended color
  if (terminologyInfo.GeneratedColor.isValid())
    {
    d->GeneratedColor = terminologyInfo.GeneratedColor;
    }
 
  // Set color
  if (!terminologyInfo.Color.isValid())
    {
    // If given color is invalid, then get it from terminology (auto-generate flag will be true)
    d->setRecommendedColorFromCurrentTerminology();
    }
  else
    {
    d->ColorPickerButton_RecommendedRGB->blockSignals(true); // Only call callback function if user changes from UI
    d->ColorPickerButton_RecommendedRGB->setColor(terminologyInfo.Color);
    d->ColorPickerButton_RecommendedRGB->blockSignals(false);

    d->ColorAutoGenerated = terminologyInfo.ColorAutoGenerated;
    d->pushButton_ResetColor->setEnabled(!d->ColorAutoGenerated && !noneType);
    }
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::terminologyEntry(vtkSlicerTerminologyEntry* entry)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  if (!entry)
    {
    qCritical() << Q_FUNC_INFO << ": Invalid terminology entry object";
    return false;
    }
  if (!entry->GetCategoryObject() || !entry->GetTypeObject() || !entry->GetTypeModifierObject()
    || !entry->GetAnatomicRegionObject() || !entry->GetAnatomicRegionModifierObject() )
    {
    qCritical() << Q_FUNC_INFO << ": Invalid terminology entry given";
    // Invalidate whole terminology entry
    entry->SetTerminologyContextName(nullptr);
    entry->SetAnatomicContextName(nullptr);
    return false;
    }

  // Terminology name
  if (d->CurrentTerminologyName.isEmpty())
    {
    qCritical() << Q_FUNC_INFO << ": No terminology selected";
    return false;
    }
  entry->SetTerminologyContextName(d->CurrentTerminologyName.toUtf8().constData());

  // Terminology category
  if (!d->CurrentCategoryObject)
    {
    qCritical() << Q_FUNC_INFO << ": No terminology category selected";
    return false;
    }
  entry->GetCategoryObject()->Copy(d->CurrentCategoryObject);

  // Terminology type
  if (!d->CurrentTypeObject)
    {
    qCritical() << Q_FUNC_INFO << ": No terminology type selected";
    return false;
    }
  entry->GetTypeObject()->Copy(d->CurrentTypeObject);

  // Terminology type modifier
  if (d->CurrentTypeModifierObject)
    {
    entry->GetTypeModifierObject()->Copy(d->CurrentTypeModifierObject);
    }
  else
    {
    entry->GetTypeModifierObject()->Initialize();
    }

  // Anatomic context name
  if (!d->CurrentAnatomicContextName.isEmpty())
    {
    entry->SetAnatomicContextName(d->CurrentAnatomicContextName.toUtf8().constData());
    }

  // Anatomic region
  if (d->CurrentRegionObject)
    {
    entry->GetAnatomicRegionObject()->Copy(d->CurrentRegionObject);
    }
  else
    {
    entry->GetAnatomicRegionObject()->Initialize();
    }

  // Anatomic region modifier
  if (d->CurrentRegionModifierObject)
    {
    entry->GetAnatomicRegionModifierObject()->Copy(d->CurrentRegionModifierObject);
    }
  else
    {
    entry->GetAnatomicRegionModifierObject()->Initialize();
    }

  return true;
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::setTerminologyEntry(vtkSlicerTerminologyEntry* entry)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  if (!entry)
    {
    qCritical() << Q_FUNC_INFO << ": Invalid terminology entry object";
    return false;
    }

  // If entry is empty then select none type for no terminology
  if (!entry->GetTerminologyContextName() && !entry->GetCategoryObject()->GetCodeValue())
    {
    this->setCurrentType(nullptr);
    return true;
    }

  // Select terminology
  QString terminologyContextName(entry->GetTerminologyContextName()?entry->GetTerminologyContextName():"");
  if (terminologyContextName.isEmpty())
    {
    QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings();
    if (settings->contains("Terminology/LastTerminologyContext"))
      {
      QString lastTerminologyContextName = settings->value("Terminology/LastTerminologyContext").toString();
      if (!lastTerminologyContextName.isEmpty())
        {
        terminologyContextName = lastTerminologyContextName;
        }
      else
        {
        return false; // The terminology is not invalid but empty
        }
      }
    }
  int terminologyIndex = d->ComboBox_Terminology->findText(terminologyContextName);
  if (terminologyIndex == -1)
    {
    qCritical() << Q_FUNC_INFO << ": Failed to find terminology with context name " << terminologyContextName;
    return false;
    }
  if (terminologyIndex != d->ComboBox_Terminology->currentIndex())
    {
    this->setCurrentTerminology(d->ComboBox_Terminology->itemText(terminologyIndex));
    }
  d->ComboBox_Terminology->blockSignals(true);
  d->ComboBox_Terminology->setCurrentIndex(terminologyIndex);
  d->ComboBox_Terminology->blockSignals(false);

  // Select category
  vtkSlicerTerminologyCategory* categoryObject = entry->GetCategoryObject();
  if (!categoryObject)
    {
    return false; // The terminology is not invalid but empty
    }
  bool returnValue = true;
  if (!this->setCurrentCategory(categoryObject))
    {
    qCritical() << Q_FUNC_INFO << ": Failed to find category with name " << (categoryObject->GetCodeMeaning()?categoryObject->GetCodeMeaning():"NULL");
    returnValue = false;
    }

  // Select type
  vtkSlicerTerminologyType* typeObject = entry->GetTypeObject();
  if (!typeObject)
    {
    qCritical() << Q_FUNC_INFO << ": No type object in terminology entry";
    returnValue = false;
    }
  else if (!this->setCurrentType(typeObject))
    {
    qCritical() << Q_FUNC_INFO << ": Failed to find type with name " << (typeObject->GetCodeMeaning()?typeObject->GetCodeMeaning():"NULL");
    returnValue = false;
    }

  // Select type modifier
  vtkSlicerTerminologyType* typeModifierObject = entry->GetTypeModifierObject();
  if (typeObject && typeObject->GetHasModifiers() && typeModifierObject && typeModifierObject->GetCodeValue())
    {
    if (!this->setCurrentTypeModifier(typeModifierObject))
      {
      qCritical() << Q_FUNC_INFO << ": Failed to find type modifier with name " << (typeModifierObject->GetCodeMeaning()?typeModifierObject->GetCodeMeaning():"NULL");
      returnValue = false;
      }
    }

  // Set anatomic context selection if category allows
  if (categoryObject->GetShowAnatomy())
    {
    // Select anatomic context
    QString anatomicContextName(entry->GetAnatomicContextName()?entry->GetAnatomicContextName():"");
    if (anatomicContextName.isEmpty())
      {
      QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings();
      if (settings->contains("Terminology/LastAnatomicContext"))
        {
        QString lastAnatomicContextName = settings->value("Terminology/LastAnatomicContext").toString();
        if (!lastAnatomicContextName.isEmpty())
          {
          anatomicContextName = lastAnatomicContextName;
          }
        }
      }
    if (!anatomicContextName.isEmpty()) // Optional
      {
      int anatomicContextIndex = d->ComboBox_AnatomicContext->findText(anatomicContextName);
      if (anatomicContextIndex == -1)
        {
        qCritical() << Q_FUNC_INFO << ": Failed to find anatomic context with context name " << anatomicContextName;
        returnValue = false;
        }
      if (anatomicContextIndex != d->ComboBox_AnatomicContext->currentIndex())
        {
        this->setCurrentAnatomicContext(d->ComboBox_AnatomicContext->itemText(anatomicContextIndex));
        }
      d->ComboBox_AnatomicContext->blockSignals(true);
      d->ComboBox_AnatomicContext->setCurrentIndex(anatomicContextIndex);
      d->ComboBox_AnatomicContext->blockSignals(false);
      }

    // Select region
    vtkSlicerTerminologyType* regionObject = entry->GetAnatomicRegionObject();
    if (regionObject) // Optional
      {
      this->setCurrentRegion(regionObject);

      // Select region modifier
      vtkSlicerTerminologyType* regionModifierObject = entry->GetAnatomicRegionModifierObject();
      if (regionObject->GetHasModifiers() && regionModifierObject)
        {
        this->setCurrentRegionModifier(regionModifierObject);
        }
      } // If region is selected
    } // If showAnatomy is true
  else
    {
    // Set anatomic context combobox selection to the last selected context anyway, so that when
    // the user changes category, the selected anatomic context is the last selected one
    QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings();
    if (settings->contains("Terminology/LastAnatomicContext"))
      {
      QString lastAnatomicContextName = settings->value("Terminology/LastAnatomicContext").toString();
      int lastAnatomicContextIndex = d->ComboBox_AnatomicContext->findText(lastAnatomicContextName);
      if (lastAnatomicContextIndex >= 0)
        {
        d->ComboBox_AnatomicContext->blockSignals(true);
        d->ComboBox_AnatomicContext->setCurrentIndex(lastAnatomicContextIndex);
        d->ComboBox_AnatomicContext->blockSignals(false);

        this->setCurrentAnatomicContext(lastAnatomicContextName);
        }
      }
    }

  return returnValue;
}

//-----------------------------------------------------------------------------
QString qSlicerTerminologyNavigatorWidget::nameFromTerminology(vtkSlicerTerminologyEntry* entry)
{
  QString name;
  if ( !entry->GetTypeObject() || !entry->GetTypeObject()->GetCodeValue() ||
       (entry->GetTypeObject()->GetHasModifiers() && !entry->GetTypeModifierObject()) )
    {
    // Incomplete terminology selection, name is empty
    return name;
    }

  // Try to set name based on '3dSlicerLabel' field in terminology entry
  if ( entry->GetTypeObject()->GetSlicerLabel()
    && (!entry->GetTypeObject()->GetHasModifiers() || entry->GetTypeModifierObject()->GetCodeValue() == nullptr) )
    {
    name = entry->GetTypeObject()->GetSlicerLabel();
    }
  else if (entry->GetTypeModifierObject()->GetSlicerLabel())
    {
    name = entry->GetTypeModifierObject()->GetSlicerLabel();
    }
  // Assemble from selection if there is no label
  if (name.isEmpty())
    {
    name = entry->GetTypeObject()->GetCodeMeaning();

    if (entry->GetTypeObject()->GetHasModifiers() && entry->GetTypeModifierObject() && entry->GetTypeModifierObject()->GetCodeValue())
      {
      name += QString(", %1").arg(entry->GetTypeModifierObject()->GetCodeMeaning());
      }
    }

  if (name.isEmpty())
    {
    qCritical() << Q_FUNC_INFO << ": Failed to generate name";
    }
  return name;
}

//-----------------------------------------------------------------------------
QString qSlicerTerminologyNavigatorWidget::nameFromCurrentTerminology()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  if ( !d->CurrentTypeObject ||
       (d->CurrentTypeObject->GetHasModifiers() && !d->CurrentTypeModifierObject) )
    {
    // Incomplete terminology selection, name is empty
    return QString();
    }
  vtkSmartPointer<vtkSlicerTerminologyEntry> terminologyEntry = vtkSmartPointer<vtkSlicerTerminologyEntry>::New();
  if (!this->terminologyEntry(terminologyEntry))
    {
    // Failed to get current terminology
    return QString();
    }

  return qSlicerTerminologyNavigatorWidget::nameFromTerminology(terminologyEntry);
}

//-----------------------------------------------------------------------------
QColor qSlicerTerminologyNavigatorWidget::recommendedColorFromTerminology(vtkSlicerTerminologyEntry* entry)
{
  QColor color;
  if (!entry || !entry->GetTypeObject())
    {
    return color;
    }

  vtkSlicerTerminologyType* typeObject = entry->GetTypeObject();
  if (typeObject->GetHasModifiers())
    {
    // Get color from modifier if any
    typeObject = entry->GetTypeModifierObject();
    }

  unsigned char colorChar[3] = {0,0,0};
  typeObject->GetRecommendedDisplayRGBValue(colorChar);
  color.setRgb(colorChar[0], colorChar[1], colorChar[2]);
  return color;
}

//-----------------------------------------------------------------------------
QColor qSlicerTerminologyNavigatorWidget::recommendedColorFromCurrentTerminology()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  QColor color;
  if (d->CurrentAnatomicContextName.isEmpty() || !d->CurrentCategoryObject || !d->CurrentTypeObject)
    {
    qWarning() << Q_FUNC_INFO << ": Invalid current terminology";
    return color;
    }

  vtkSlicerTerminologyType* typeObject = d->CurrentTypeObject;
  if (d->CurrentTypeObject->GetHasModifiers())
    {
    // Get color from modifier if any
    typeObject = d->CurrentTypeModifierObject;
    }

  unsigned char colorChar[3] = {0,0,0};
  typeObject->GetRecommendedDisplayRGBValue(colorChar);
  color.setRgb(colorChar[0], colorChar[1], colorChar[2]);
  return color;
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::anatomicRegionSectionVisible() const
{
  Q_D(const qSlicerTerminologyNavigatorWidget);

  return d->AnatomicalRegionExpandButton->isChecked();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::setAnatomicRegionSectionVisible(bool visible)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  d->AnatomicalRegionExpandButton->setChecked(visible);
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::overrideSectionVisible() const
{
  Q_D(const qSlicerTerminologyNavigatorWidget);
  return d->frame_TerminologyOverride->isVisible();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::setOverrideSectionVisible(bool visible)
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->frame_TerminologyOverride->setVisible(visible);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::populateTerminologyComboBox()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  d->ComboBox_Terminology->clear();

  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    return;
    }

  d->TerminologyComboboxPopulating = true;
  std::vector<std::string> terminologyNames;
  logic->GetLoadedTerminologyNames(terminologyNames);
  for (std::vector<std::string>::iterator termIt=terminologyNames.begin(); termIt!=terminologyNames.end(); ++termIt)
    {
    d->ComboBox_Terminology->addItem(termIt->c_str());
    }
  d->TerminologyComboboxPopulating = false;
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::populateCategoryTable()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  d->tableWidget_Category->clearContents();

  if (d->CurrentTerminologyName.isEmpty())
    {
    d->tableWidget_Category->setRowCount(0);
    return;
    }

  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
    }

  // Get category names containing the search string. If no search string then add every category
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier> categories;
  logic->FindCategoriesInTerminology(
    d->CurrentTerminologyName.toUtf8().constData(), categories, d->SearchBox_Category->text().toUtf8().constData() );

  QTableWidgetItem* selectedItem = nullptr;
  d->tableWidget_Category->setRowCount(categories.size());
  int index = 0;
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier>::iterator idIt;
  for (idIt=categories.begin(); idIt!=categories.end(); ++idIt, ++index)
    {
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedCategoryId = (*idIt);
    QString addedCategoryName(addedCategoryId.CodeMeaning.c_str());
    QTableWidgetItem* addedCategoryItem = new QTableWidgetItem(addedCategoryName);
    addedCategoryItem->setData(CodingSchemeDesignatorRole, QString(addedCategoryId.CodingSchemeDesignator.c_str()));
    addedCategoryItem->setData(CodeValueRole, QString(addedCategoryId.CodeValue.c_str()));
    d->tableWidget_Category->setItem(index, 0, addedCategoryItem);

    if ( d->CurrentCategoryObject->GetCodingSchemeDesignator() && !addedCategoryId.CodingSchemeDesignator.compare(d->CurrentCategoryObject->GetCodingSchemeDesignator())
      && d->CurrentCategoryObject->GetCodeValue() && !addedCategoryId.CodeValue.compare(d->CurrentCategoryObject->GetCodeValue()) )
      {
      selectedItem = addedCategoryItem;
      }
    }

  // Select category if selection was valid and item shows up in search
  if (selectedItem)
    {
    d->tableWidget_Category->setCurrentItem(selectedItem);
    }
  // Otherwise select all
  else
    {
    d->tableWidget_Category->selectAll();
    }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::populateTypeTable()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  d->tableWidget_Type->clearContents();

  // Collect selected categories. Use current category if selected list is empty and current is valid
  // (selection happens from UI, current is set when setting from outside using setTerminologyEntry)
  QList<vtkSlicerTerminologyCategory*> selectedCategories;
  foreach (vtkSmartPointer<vtkSlicerTerminologyCategory> category, d->SelectedCategoryObjects)
    {
    selectedCategories << category.GetPointer();
    }
  if (!selectedCategories.count() && d->CurrentCategoryObject && d->CurrentCategoryObject->GetCodeValue())
    {
    selectedCategories << d->CurrentCategoryObject;
    }

  // Empty table only contains none item
  if (d->CurrentTerminologyName.isEmpty() || selectedCategories.count() == 0)
    {
    d->tableWidget_Type->setRowCount(1);
    QTableWidgetItem* noneItem = new QTableWidgetItem(d->NoneItemName);
    d->tableWidget_Type->setItem(0, 0, noneItem);
    d->tableWidget_Type->setCurrentItem(noneItem);
    return;
    }

  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
    }

  // Get types in selected categories containing the search string. If no search string then add every type
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier> types;
  QMap<int, int> typeIndexToCategoryIndexMap;
  int typeIndex = 0;
  int categoryIndex = 0;
  std::string searchTerm(d->SearchBox_Type->text().toUtf8().constData());
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier>::iterator idIt;
  foreach (vtkSlicerTerminologyCategory* category, selectedCategories)
    {
    std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier> typesInCategory;
    logic->FindTypesInTerminologyCategory(
      d->CurrentTerminologyName.toUtf8().constData(),
      vtkSlicerTerminologiesModuleLogic::CodeIdentifierFromTerminologyCategory(category),
      typesInCategory, searchTerm );

    for (idIt=typesInCategory.begin(); idIt!=typesInCategory.end(); ++idIt)
      {
      // Determine if type already exists in list
      bool duplicate = false;
      std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier>::iterator typeIt;
      for (typeIt=types.begin(); typeIt!=types.end(); ++typeIt)
        {
        if ( !idIt->CodeValue.compare(typeIt->CodeValue)
          && !idIt->CodingSchemeDesignator.compare(typeIt->CodingSchemeDesignator) )
          {
          duplicate = true;
          break;
          }
        }
      if (!duplicate)
        {
        // Add type
        types.push_back(*idIt);

        // Store type-category relationship
        typeIndexToCategoryIndexMap[typeIndex] = categoryIndex;

        typeIndex++;
        }
      }

    ++categoryIndex;
    }

    QTableWidgetItem* selectedItem = nullptr;

    // Show none item only if search term is empty (if user is searching then they want an actual type)
    int noneTypeExists = 0;
    QTableWidgetItem* noneItem = nullptr;
    if (searchTerm.empty())
    {
      noneTypeExists = 1;
      noneItem = new QTableWidgetItem(d->NoneItemName);
      d->tableWidget_Type->setRowCount(types.size() + noneTypeExists);
      d->tableWidget_Type->setItem(0, 0, noneItem);
    }
    else
    {
      d->tableWidget_Type->setRowCount(types.size());
    }

    // Add type items to table
    typeIndex = 0;
    for (idIt=types.begin(); idIt!=types.end(); ++idIt, ++typeIndex)
      {
      vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedTypeId = (*idIt);
      QString addedTypeName(addedTypeId.CodeMeaning.c_str());
      QTableWidgetItem* addedTypeItem = new QTableWidgetItem(addedTypeName);
      addedTypeItem->setData(CodingSchemeDesignatorRole, QString(addedTypeId.CodingSchemeDesignator.c_str()));
      addedTypeItem->setData(CodeValueRole, QString(addedTypeId.CodeValue.c_str()));
      // Reference containing category so that it can be set when type is selected
      vtkSlicerTerminologyCategory* category = selectedCategories[typeIndexToCategoryIndexMap[typeIndex]];
      addedTypeItem->setData(CategoryCodingSchemeDesignatorRole, QString(category->GetCodingSchemeDesignator()));
      addedTypeItem->setData(CategoryCodeValueRole, QString(category->GetCodeValue()));
      addedTypeItem->setData(CategoryCodeMeaningRole, QString(category->GetCodeMeaning()));
      QString tooltip = QString("Category: %1 (anatomy:%2)").arg(category->GetCodeMeaning()).arg(
        (category->GetShowAnatomy() ? "available" : "N/A") );
      addedTypeItem->setToolTip(tooltip);
      // Insert type item
      d->tableWidget_Type->setItem(typeIndex + noneTypeExists, 0, addedTypeItem);

      if ( d->CurrentTypeObject->GetCodingSchemeDesignator() && !addedTypeId.CodingSchemeDesignator.compare(d->CurrentTypeObject->GetCodingSchemeDesignator())
        && d->CurrentTypeObject->GetCodeValue() && !addedTypeId.CodeValue.compare(d->CurrentTypeObject->GetCodeValue()) )
        {
        selectedItem = addedTypeItem;
        }
      }

  if (selectedItem)
    {
    // Select type if selection was valid and item shows up in search
    d->tableWidget_Type->setCurrentItem(selectedItem);
    }
  else if (noneItem)
    {
    // Select none item if it exists (no search and no selected item specified)
    d->tableWidget_Type->setCurrentItem(noneItem);
    }
  else
    {
    // Select first type otherwise
    d->tableWidget_Type->setCurrentCell(0,0);
    }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::populateTypeModifierComboBox()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  d->ComboBox_TypeModifier->clear();

  if (d->CurrentTerminologyName.isEmpty() || !d->CurrentCategoryObject || !d->CurrentTypeObject || !d->CurrentTypeObject->GetCodeValue())
    {
    d->ComboBox_TypeModifier->setEnabled(false);
    return;
    }
  // If current type has no modifiers then leave it empty and disable
  if (!d->CurrentTypeObject->GetHasModifiers())
    {
    d->ComboBox_TypeModifier->setEnabled(false);
    return;
    }

  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
    }

  int selectedIndex = -1;

  // Add none item so that no modifier can be selected even if there are options
  d->ComboBox_TypeModifier->addItem(tr("No type modifier"));
  if (d->CurrentTypeModifierObject->GetCodeValue() == nullptr)
    {
    selectedIndex = 0;
    }

  // Get type modifier names
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier> typeModifiers;
  logic->GetTypeModifiersInTerminologyType(
    d->CurrentTerminologyName.toUtf8().constData(),
    vtkSlicerTerminologiesModuleLogic::CodeIdentifierFromTerminologyCategory(d->CurrentCategoryObject),
    vtkSlicerTerminologiesModuleLogic::CodeIdentifierFromTerminologyType(d->CurrentTypeObject),
    typeModifiers );

  int index = 1; // None modifier has index 0
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier>::iterator idIt;
  for (idIt=typeModifiers.begin(); idIt!=typeModifiers.end(); ++idIt, ++index)
    {
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedTypeModifierId = (*idIt);
    QString addedTypeModifierName(addedTypeModifierId.CodeMeaning.c_str());

    QMap<QString, QVariant> userData;
    userData[QString::number(CodingSchemeDesignatorRole)] = QString(addedTypeModifierId.CodingSchemeDesignator.c_str());
    userData[QString::number(CodeValueRole)] = QString(addedTypeModifierId.CodeValue.c_str());
    d->ComboBox_TypeModifier->addItem(addedTypeModifierName, QVariant(userData));
    
    if (selectedIndex == -1 && !addedTypeModifierName.compare(d->CurrentTypeModifierObject->GetCodeMeaning()))
      {
      selectedIndex = index;
      }
    }

  // Select modifier if selection was valid
  if (selectedIndex != -1)
    {
    d->ComboBox_TypeModifier->setCurrentIndex(selectedIndex);
    }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::setCurrentTerminology(QString terminologyName)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  // If no change then nothing to do
  if (d->CurrentTerminologyName == terminologyName)
    {
    return;
    }

  // Reset current category, type, and type modifier
  d->resetCurrentCategory();
  d->resetCurrentType();
  d->resetCurrentTypeModifier();

  // Set current terminology
  d->CurrentTerminologyName = terminologyName;
  if (terminologyName.isEmpty())
    {
    return;
    }

  // Populate category table, and reset type table and type modifier combobox
  this->populateCategoryTable();
  this->populateTypeTable();
  this->populateTypeModifierComboBox();

  // Only enable category table if there are items in it
  if (d->tableWidget_Category->rowCount() == 0)
    {
    d->tableWidget_Category->setEnabled(!d->SearchBox_Type->text().isEmpty()); // Might be empty because of a search
    //d->tableWidget_Type->setEnabled(false);
    d->SearchBox_Type->setEnabled(false);
    d->ComboBox_TypeModifier->setEnabled(false);
    }
  else
    {
    d->tableWidget_Category->setEnabled(true);
    d->SearchBox_Category->setEnabled(true);
    }
  
  // Selection is valid if there is a valid type object
  emit selectionValidityChanged(d->CurrentTypeObject && d->CurrentTypeObject->GetCodeValue());
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onTerminologySelectionChanged(int index)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Set current terminology
  QString terminologyName = d->ComboBox_Terminology->itemText(index);
  this->setCurrentTerminology(terminologyName);

  // Save last selection to application settings
  if (!d->TerminologyComboboxPopulating)
    {
    QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings();
    settings->setValue("Terminology/LastTerminologyContext", terminologyName);
    }
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::setCurrentCategory(vtkSlicerTerminologyCategory* category)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Reset current type and type modifier
  d->resetCurrentType();
  d->resetCurrentTypeModifier();
  // Reset anatomic region information as well
  d->resetCurrentRegion();
  d->resetCurrentRegionModifier();

  if (!category)
    {
    d->resetCurrentCategory();
    qCritical() << Q_FUNC_INFO << ": Invalid category object set";
    return false;
    }

  // Set current category
  d->CurrentCategoryObject->Copy(category);

  // Populate type table, and reset type modifier combobox and region widgets
  this->populateTypeTable();
  this->populateTypeModifierComboBox();
  d->tableWidget_AnatomicRegion->setCurrentItem(nullptr);
  this->populateRegionModifierComboBox();

  // Update widget UI from current category
  this->updateWidgetFromCurrentCategory();

  // Selection is invalid until type is selected
  emit selectionValidityChanged(false);

  // Select category if found
  QTableWidgetItem* categoryItem = d->findTableWidgetItemForCategory(category);
  //if (categoryItem)
  //  {
  //  d->tableWidget_Category->blockSignals(true);
  //  d->tableWidget_Category->clearSelection();
  //  d->tableWidget_Category->setCurrentItem(categoryItem);
  //  d->tableWidget_Category->blockSignals(false);
  //  }
  return categoryItem; // Return true if category found and selected
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::updateWidgetFromCurrentCategory()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Only enable type table if there are items in it
  if (d->tableWidget_Type->rowCount() == 1) // None item always present
    {
    //d->tableWidget_Type->setEnabled(!d->SearchBox_Type->text().isEmpty()); // Might be empty because of a search
    d->ComboBox_TypeModifier->setEnabled(false);
    }
  else
    {
    //d->tableWidget_Type->setEnabled(true);
    d->SearchBox_Type->setEnabled(true);
    }

  // Enable anatomic region controls if related flag is on
  d->ComboBox_AnatomicContext->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
  d->tableWidget_AnatomicRegion->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
  d->SearchBox_AnatomicRegion->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
  d->ComboBox_AnatomicRegionModifier->setEnabled(false); // Disabled until valid region selection
  if (this->anatomicRegionSectionVisible())
    {
    // Always enable expand button if panel is visible
    d->AnatomicalRegionExpandButton->setEnabled(true);
    }
  else
    {
    // Only enable expand button if anatomic region is enabled in selected category
    d->AnatomicalRegionExpandButton->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
    // Blink button if anatomic region is enabled to let the user know there are additional options available
    if (d->CurrentCategoryObject->GetShowAnatomy())
      {
      d->AnatomicalRegionExpandButton->setDown(true);
      QTimer::singleShot(50, this, SLOT(anatomicalRegionExpandButtonUp()));
      QTimer::singleShot(100, this, SLOT(anatomicalRegionExpandButtonDown()));
      QTimer::singleShot(150, this, SLOT(anatomicalRegionExpandButtonUp()));
      QTimer::singleShot(200, this, SLOT(anatomicalRegionExpandButtonDown()));
      QTimer::singleShot(250, this, SLOT(anatomicalRegionExpandButtonUp()));
      }
    }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onCategorySelectionChanged()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  QList<QTableWidgetItem*> selectedItems = d->tableWidget_Category->selectedItems();

  if (selectedItems.count() == 0)
    {
    // Happens when clearing the table
    return;
    }

  // Reset current category because it will be set when the type is selected
  d->resetCurrentCategory();
  // Reset current type and type modifier
  d->resetCurrentType();
  d->resetCurrentTypeModifier();
  // Reset anatomic region information as well
  d->resetCurrentRegion();
  d->resetCurrentRegionModifier();

  // Get current category object
  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
    }

  bool showAnatomyOnInAnyCategories = false;

  d->SelectedCategoryObjects.clear();
  foreach (QTableWidgetItem* currentItem, selectedItems)
    {
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId(
      currentItem->data(CodingSchemeDesignatorRole).toString().toUtf8().constData(),
      currentItem->data(CodeValueRole).toString().toUtf8().constData(),
      currentItem->text().toUtf8().constData() );
    vtkSmartPointer<vtkSlicerTerminologyCategory> category = vtkSmartPointer<vtkSlicerTerminologyCategory>::New();
    if (!logic->GetCategoryInTerminology(
      d->CurrentTerminologyName.toUtf8().constData(), categoryId, category) )
      {
      qCritical() << Q_FUNC_INFO << ": Failed to find category '" << currentItem->text();
      continue;
      }

    showAnatomyOnInAnyCategories |= category->GetShowAnatomy();

    d->SelectedCategoryObjects << category;
    }

  // Populate type table, and reset type modifier combobox and region widgets
  this->populateTypeTable();
  this->populateTypeModifierComboBox();
  this->populateRegionModifierComboBox();

  // Only enable type table if there are items in it
  if (d->tableWidget_Type->rowCount() == 1) // None item always present
    {
    d->ComboBox_TypeModifier->setEnabled(false);
    }
  else
    {
    d->SearchBox_Type->setEnabled(true);
    }

  // Enable anatomic region controls if related flag is on
  d->ComboBox_AnatomicContext->setEnabled(showAnatomyOnInAnyCategories);
  d->tableWidget_AnatomicRegion->setEnabled(showAnatomyOnInAnyCategories);
  d->SearchBox_AnatomicRegion->setEnabled(showAnatomyOnInAnyCategories);
  d->ComboBox_AnatomicRegionModifier->setEnabled(false); // Disabled until valid region selection

  // Selection is invalid until type is selected
  emit selectionValidityChanged(false);

  // Generate name based on selection (empty name in this case) if not custom
  if (d->NameAutoGenerated)
    {
    d->setNameFromCurrentTerminology();
    }
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::setCurrentType(vtkSlicerTerminologyType* type)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Reset current type modifier
  d->resetCurrentTypeModifier();

  // Null type means None selection
  if (!type)
    {
    d->resetCurrentType();
    d->tableWidget_Type->blockSignals(true);
    d->tableWidget_Type->setCurrentCell(0,0);
    d->tableWidget_Type->blockSignals(false);
    emit selectionValidityChanged(true); // None selection is valid too
    return false;
    }

  // Set current type
  d->CurrentTypeObject->Copy(type);

  // Populate type modifier combobox
  this->populateTypeModifierComboBox();

  // Only enable type modifier combobox if there are items in it
  d->ComboBox_TypeModifier->setEnabled(d->ComboBox_TypeModifier->count());

  // With valid type selected, terminology selection becomes also valid
  emit selectionValidityChanged(true);

  // Select type if found
  QTableWidgetItem* typeItem = d->findTableWidgetItemForType(d->tableWidget_Type, type);
  if (typeItem)
    {
    d->tableWidget_Type->blockSignals(true);
    d->tableWidget_Type->setCurrentItem(typeItem);
    d->tableWidget_Type->blockSignals(false);
    }
  return typeItem; // Return true if type found and selected
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onTypeSelected(QTableWidgetItem* currentItem, QTableWidgetItem* previousItem)
{
  Q_UNUSED(previousItem)
  Q_D(qSlicerTerminologyNavigatorWidget);

  if (!currentItem)
    {
    d->resetCurrentType();
    d->resetCurrentTypeModifier();
    return;
    }

  // Reset terminology elements if none item is selected
  if (!currentItem->text().compare(d->NoneItemName))
    {
    // Do not reset category because it invalidates entry when setting terminology
    // programmatically using setTerminologyEntry
    d->resetCurrentType();
    d->resetCurrentTypeModifier();
    d->resetCurrentRegion();
    d->resetCurrentRegionModifier();
    return;
    }

  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
    }

  // Get category object for selected type object
  vtkSlicerTerminologiesModuleLogic::CodeIdentifier categoryId(
    currentItem->data(CategoryCodingSchemeDesignatorRole).toString().toUtf8().constData(),
    currentItem->data(CategoryCodeValueRole).toString().toUtf8().constData(),
    currentItem->data(CategoryCodeMeaningRole).toString().toUtf8().constData() );
  vtkSmartPointer<vtkSlicerTerminologyCategory> category = vtkSmartPointer<vtkSlicerTerminologyCategory>::New();
  if (!logic->GetCategoryInTerminology(
    d->CurrentTerminologyName.toUtf8().constData(),
    categoryId, category) )
    {
    qCritical() << Q_FUNC_INFO << ": Failed to find category '" << categoryId.CodeMeaning.c_str();
    return;
    }

  // Get current type object
  vtkSlicerTerminologiesModuleLogic::CodeIdentifier typeId(
    currentItem->data(CodingSchemeDesignatorRole).toString().toUtf8().constData(),
    currentItem->data(CodeValueRole).toString().toUtf8().constData(),
    currentItem->text().toUtf8().constData() );
  vtkSmartPointer<vtkSlicerTerminologyType> type = vtkSmartPointer<vtkSlicerTerminologyType>::New();
  if (!logic->GetTypeInTerminologyCategory(
    d->CurrentTerminologyName.toUtf8().constData(), categoryId, typeId, type) )
    {
    qCritical() << Q_FUNC_INFO << ": Failed to find type '" << currentItem->text();
    return;
    }

  // Set current category object for selected type
  d->CurrentCategoryObject->Copy(category);
  this->updateWidgetFromCurrentCategory();
  // Set type from item
  this->setCurrentType(type);

  // Update state of anatomy controls based on the category of the selected type
  d->ComboBox_AnatomicContext->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
  d->tableWidget_AnatomicRegion->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
  d->SearchBox_AnatomicRegion->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
  d->ComboBox_AnatomicRegionModifier->setEnabled(false); // Disabled until valid region selection

  // Generate name based on selection if not custom
  if (d->NameAutoGenerated)
    {
    d->setNameFromCurrentTerminology();
    }
  // Set recommended color to color picker if not custom
  if (d->ColorAutoGenerated)
    {
    d->setRecommendedColorFromCurrentTerminology();
    }
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::setCurrentTypeModifier(vtkSlicerTerminologyType* modifier)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  if (!modifier)
    {
    d->resetCurrentTypeModifier();
    qCritical() << Q_FUNC_INFO << ": Invalid type modifier object set";
    return false;
    }

  // Set current type modifier
  d->CurrentTypeModifierObject->Copy(modifier);

  // Select modifier if found
  int modifierIndex = d->findComboBoxIndexForModifier(d->ComboBox_TypeModifier, modifier);
  if (modifierIndex != -1)
    {
    d->ComboBox_TypeModifier->blockSignals(true);
    d->ComboBox_TypeModifier->setCurrentIndex(modifierIndex);
    d->ComboBox_TypeModifier->blockSignals(false);
    }
  return (modifierIndex != -1);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onTypeModifierSelectionChanged(int index)
{
  Q_UNUSED(index);
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Invalidate type modifier if there are no modifiers for the current type
  if (d->ComboBox_TypeModifier->count() == 0)
    {
    d->resetCurrentTypeModifier();
    return;
    }

  vtkSmartPointer<vtkSlicerTerminologyType> modifier = vtkSmartPointer<vtkSlicerTerminologyType>::New();
  if (index < 0)
    {
    // If new index is invalid (happens on clearing the combobox), then set empty modifier
    this->setCurrentTypeModifier(modifier);
    return;
    }

  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
    }

  // Get current modifier object is not None modifier is selected
  if (index != 0 || !d->ComboBox_TypeModifier->itemData(index).isNull())
    {
    QMap<QString, QVariant> userData = d->ComboBox_TypeModifier->itemData(index).toMap();
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier modifierId(
      userData[QString::number(CodingSchemeDesignatorRole)].toString().toUtf8().constData(),
      userData[QString::number(CodeValueRole)].toString().toUtf8().constData(),
      d->ComboBox_TypeModifier->itemText(index).toUtf8().constData() );
    if (!logic->GetTypeModifierInTerminologyType(
      d->CurrentTerminologyName.toUtf8().constData(),
      vtkSlicerTerminologiesModuleLogic::CodeIdentifierFromTerminologyCategory(d->CurrentCategoryObject),
      vtkSlicerTerminologiesModuleLogic::CodeIdentifierFromTerminologyType(d->CurrentTypeObject),
      modifierId, modifier) )
      {
      qCritical() << Q_FUNC_INFO << ": Failed to find modifier '" << d->ComboBox_TypeModifier->itemText(index);
      return;
      }
    }

  // Set current type modifier
  this->setCurrentTypeModifier(modifier);

  // Generate name based on selection if not custom
  if (d->NameAutoGenerated)
    {
    d->setNameFromCurrentTerminology();
    }
  // Set recommended color to color picker if not custom
  if (d->ColorAutoGenerated)
    {
    d->setRecommendedColorFromCurrentTerminology();
    }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onCategorySearchTextChanged(QString search)
{
  Q_UNUSED(search);

  this->populateCategoryTable();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onTypeSearchTextChanged(QString search)
{
  Q_UNUSED(search);

  this->populateTypeTable();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onNameChanged(QString name)
{
  Q_UNUSED(name);
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Set as manual
  d->NameAutoGenerated = false;

  // Enable reset name button
  d->pushButton_ResetName->setEnabled(true);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onResetNameClicked()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->setNameFromCurrentTerminology();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onColorChanged(QColor color)
{
  Q_UNUSED(color);
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Set as manual
  d->ColorAutoGenerated = false;

  // Enable reset color button
  d->pushButton_ResetColor->setEnabled(true);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onResetColorClicked()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->setRecommendedColorFromCurrentTerminology();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onLoadTerminologyClicked()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  const QString& terminologyFileName =
    QFileDialog::getOpenFileName( this, "Select terminology json file...", QString(),
      "Json files (*.json);; All files (*)" );
  if (!terminologyFileName.isEmpty())
    {
    vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
    if (!logic)
      {
      qCritical() << Q_FUNC_INFO << ": Invalid terminology logic";
      return;
      }
    QString loadedContextName = logic->LoadTerminologyFromFile(terminologyFileName.toUtf8().constData()).c_str();
    if (!loadedContextName.isEmpty())
      {
      QMessageBox::information(this, "Load succeeded",
        QString("Loading terminology context named %1 succeeded").arg(loadedContextName) );

      this->copyContextToUserDirectory(terminologyFileName);
      }
    else
      {
      QString errorMessage =
        "Loading terminology from file %1 failed!<br><br>"
        "Please check validity of the file using the <a href=\"http://qiicr.org/dcmqi/#/validators\">online validator tool</a>.";
      QMessageBox::critical(this, "Load failed", errorMessage.arg(terminologyFileName));
      }
    }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onLoadAnatomicContextClicked()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  const QString& anatomicContextFileName =
    QFileDialog::getOpenFileName( this, "Select anatomic context json file...", QString(),
      "Json files (*.json);; All files (*)" );
  if (!anatomicContextFileName.isEmpty())
    {
    vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
    if (!logic)
      {
      qCritical() << Q_FUNC_INFO << ": Invalid terminology logic";
      return;
      }
    QString loadedContextName = logic->LoadAnatomicContextFromFile(anatomicContextFileName.toUtf8().constData()).c_str();
    if (!loadedContextName.isEmpty())
      {
      QMessageBox::information(this, "Load succeeded",
        QString("Loading anatomic context named %1 succeeded").arg(loadedContextName) );

      this->copyContextToUserDirectory(anatomicContextFileName);
      }
    else
      {
      QString errorMessage =
        "Loading anatomic context from file %1 failed!<br><br>"
        "Please check validity of the file using the <a href=\"http://qiicr.org/dcmqi/#/validators\">online validator tool</a>.";
      QMessageBox::critical(this, "Load failed", errorMessage.arg(anatomicContextFileName));
      }
    }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::copyContextToUserDirectory(QString filePath)
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    qCritical() << Q_FUNC_INFO << ": Invalid terminology logic";
    return;
    }

  // Make sure the file can be copied to the settings folder
  QDir settingsFolder(logic->GetUserContextsPath());
  if (!settingsFolder.exists())
    {
    settingsFolder.mkpath(logic->GetUserContextsPath());
    }
  if (!settingsFolder.exists())
    {
    qCritical() << Q_FUNC_INFO << ": Settings folder '" << settingsFolder.absolutePath() << "' does not exist. Copying context file failed.";
    return;
    }
  QString fileNameOnly = QFileInfo(filePath).fileName();
  QString targetFilePath = settingsFolder.absoluteFilePath(fileNameOnly);

  // Check if there is a file with the same name in the settings folder and ask the user in that case
  if (QFile::exists(targetFilePath))
    {
    QString message = QString(tr("There is a file with name '%1' in the stored contexts.\n\n"
      "Do you wish to update the stored context file with the just loaded one?")).arg(fileNameOnly);
    QMessageBox::StandardButton answer =
      QMessageBox::question(nullptr, tr("Context file exists"), message,
      QMessageBox::Yes | QMessageBox::No, QMessageBox::No );
    if (answer == QMessageBox::No)
      {
      return;
      }
    else
      {
      // Remove file before copying (copy function does not overwrite)
      settingsFolder.remove(fileNameOnly);
      }
    }

  // Copy file to settings folder for automatic loading on startup
  QFile file(filePath);
  file.copy(targetFilePath);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::populateAnatomicContextComboBox()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  d->ComboBox_AnatomicContext->clear();

  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    return;
    }

  d->AnatomicContextComboboxPopulating = true;
  std::vector<std::string> anatomicRegionContextNames;
  logic->GetLoadedAnatomicContextNames(anatomicRegionContextNames);
  for (std::vector<std::string>::iterator anIt=anatomicRegionContextNames.begin(); anIt!=anatomicRegionContextNames.end(); ++anIt)
    {
    d->ComboBox_AnatomicContext->addItem(anIt->c_str());
    }
  d->AnatomicContextComboboxPopulating = false;
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::populateRegionTable()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  d->tableWidget_AnatomicRegion->clearContents();

  if (d->CurrentAnatomicContextName.isEmpty())
    {
    d->tableWidget_AnatomicRegion->setRowCount(0);
    return;
    }

  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
    }

  // Get region names containing the search string. If no search string then add every region
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier> regions;
  logic->FindRegionsInAnatomicContext(
    d->CurrentAnatomicContextName.toUtf8().constData(),
    regions, d->SearchBox_AnatomicRegion->text().toUtf8().constData() );

  QTableWidgetItem* selectedItem = nullptr;
  d->tableWidget_AnatomicRegion->setRowCount(regions.size());
  int index = 0;
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier>::iterator idIt;
  for (idIt=regions.begin(); idIt!=regions.end(); ++idIt, ++index)
    {
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedRegionId = (*idIt);
    QString addedRegionName(addedRegionId.CodeMeaning.c_str());
    QTableWidgetItem* addedRegionItem = new QTableWidgetItem(addedRegionName);
    addedRegionItem->setData(CodingSchemeDesignatorRole, QString(addedRegionId.CodingSchemeDesignator.c_str()));
    addedRegionItem->setData(CodeValueRole, QString(addedRegionId.CodeValue.c_str()));
    d->tableWidget_AnatomicRegion->setItem(index, 0, addedRegionItem);

    if ( d->CurrentRegionObject->GetCodingSchemeDesignator() && !addedRegionId.CodingSchemeDesignator.compare(d->CurrentRegionObject->GetCodingSchemeDesignator())
      && d->CurrentRegionObject->GetCodeValue() && !addedRegionId.CodeValue.compare(d->CurrentRegionObject->GetCodeValue()) )
      {
      selectedItem = addedRegionItem;
      }
    }

  // Select region if selection was valid and item shows up in search
  if (selectedItem)
    {
    d->tableWidget_AnatomicRegion->setCurrentItem(selectedItem);
    }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::populateRegionModifierComboBox()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  d->ComboBox_AnatomicRegionModifier->clear();

  if (d->CurrentAnatomicContextName.isEmpty() || !d->CurrentRegionObject || !d->CurrentRegionObject->GetCodeValue())
    {
    d->ComboBox_AnatomicRegionModifier->setEnabled(false);
    return;
    }
  // If current region has no modifiers then leave it empty and disable
  if (!d->CurrentRegionObject->GetHasModifiers())
    {
    d->ComboBox_AnatomicRegionModifier->setEnabled(false);
    return;
    }

  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
    }

  // Get region modifier names
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier> regionModifiers;
  logic->GetRegionModifiersInAnatomicRegion(
    d->CurrentAnatomicContextName.toUtf8().constData(),
    vtkSlicerTerminologiesModuleLogic::CodeIdentifierFromTerminologyType(d->CurrentRegionObject),
    regionModifiers );

  int selectedIndex = -1;
  int index = 0;
  std::vector<vtkSlicerTerminologiesModuleLogic::CodeIdentifier>::iterator idIt;
  for (idIt=regionModifiers.begin(); idIt!=regionModifiers.end(); ++idIt, ++index)
    {
    vtkSlicerTerminologiesModuleLogic::CodeIdentifier addedRegionModifierId = (*idIt);
    QString addedRegionModifierName(addedRegionModifierId.CodeMeaning.c_str());

    QMap<QString, QVariant> userData;
    userData[QString::number(CodingSchemeDesignatorRole)] = QString(addedRegionModifierId.CodingSchemeDesignator.c_str());
    userData[QString::number(CodeValueRole)] = QString(addedRegionModifierId.CodeValue.c_str());
    d->ComboBox_AnatomicRegionModifier->addItem(addedRegionModifierName, QVariant(userData));

    if (!addedRegionModifierName.compare(d->CurrentRegionModifierObject->GetCodeMeaning()))
      {
      selectedIndex = index;
      }
    }

  // Select modifier if selection was valid
  if (selectedIndex != -1)
    {
    d->ComboBox_AnatomicRegionModifier->setCurrentIndex(selectedIndex);
    }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::setCurrentAnatomicContext(QString contextName)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Reset current region and region modifier
  d->resetCurrentRegion();
  d->resetCurrentRegionModifier();

  // Set current anatomic context
  d->CurrentAnatomicContextName = contextName;
  if (contextName.isEmpty())
    {
    return;
    }

  // Populate region table and reset region modifier combobox
  this->populateRegionTable();
  this->populateRegionModifierComboBox();

  // Only enable region table if there are items in it
  if (d->tableWidget_AnatomicRegion->rowCount() == 0)
    {
    d->tableWidget_AnatomicRegion->setEnabled(false);
    if (d->SearchBox_AnatomicRegion->text().isEmpty())
      {
      // Table might be empty because of a search
      d->SearchBox_AnatomicRegion->setEnabled(false);
      }
    d->ComboBox_AnatomicRegionModifier->setEnabled(false);
    }
  else if (d->CurrentCategoryObject->GetShowAnatomy())
    {
    d->tableWidget_AnatomicRegion->setEnabled(true);
    d->SearchBox_AnatomicRegion->setEnabled(true);
    }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onAnatomicContextSelectionChanged(int index)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Set current anatomic context
  QString anatomicContextName = d->ComboBox_AnatomicContext->itemText(index);
  this->setCurrentAnatomicContext(anatomicContextName);

  // Save last selection to application settings
  if (!d->AnatomicContextComboboxPopulating)
    {
    QSettings* settings = qSlicerApplication::application()->settingsDialog()->settings();
    settings->setValue("Terminology/LastAnatomicContext", anatomicContextName);
    }
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::setCurrentRegion(vtkSlicerTerminologyType* region)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  // Reset current region modifier
  d->resetCurrentRegionModifier();

  if (!region)
    {
    d->resetCurrentRegion();
    qCritical() << Q_FUNC_INFO << ": Invalid region object set";
    return false;
    }

  // Ignore selection if current category does not support anatomy (possible due to multi-selection)
  if (!d->CurrentCategoryObject)
    {
    d->resetCurrentRegion();
    qCritical() << Q_FUNC_INFO << ": Missing current category";
    return false;
    }

  // Update state of anatomy controls based on the category of the selected type
  // (the controls may have been enabled because some of the selected categories supported anatomy)
  d->ComboBox_AnatomicContext->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
  d->tableWidget_AnatomicRegion->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
  d->SearchBox_AnatomicRegion->setEnabled(d->CurrentCategoryObject->GetShowAnatomy());
  d->ComboBox_AnatomicRegionModifier->setEnabled(false); // Disabled until valid region selection

  // Reject region selection if current type's category does not support anatomy
  if (!d->CurrentCategoryObject->GetShowAnatomy())
    {
    d->resetCurrentRegion();
    return false;
    }

  // Set current region
  d->CurrentRegionObject->Copy(region);

  // Populate region modifier combobox
  this->populateRegionModifierComboBox();

  // Only enable region modifier combobox if there are items in it
  d->ComboBox_AnatomicRegionModifier->setEnabled(d->ComboBox_AnatomicRegionModifier->count());

  // Select region if found
  QTableWidgetItem* regionItem = d->findTableWidgetItemForType(d->tableWidget_AnatomicRegion, region);
  if (regionItem)
    {
    d->tableWidget_AnatomicRegion->blockSignals(true);
    d->tableWidget_AnatomicRegion->setCurrentItem(regionItem);
    d->tableWidget_AnatomicRegion->blockSignals(false);
    }
  return regionItem; // Return true if region found and selected
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onRegionSelected(QTableWidgetItem* currentItem, QTableWidgetItem* previousItem)
{
  Q_UNUSED(previousItem)
  Q_D(qSlicerTerminologyNavigatorWidget);

  if (!currentItem)
    {
    d->resetCurrentRegion();
    d->resetCurrentRegionModifier();
    return;
    }

  // Get current region object
  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
    }
  vtkSlicerTerminologiesModuleLogic::CodeIdentifier regionId(
    currentItem->data(CodingSchemeDesignatorRole).toString().toUtf8().constData(),
    currentItem->data(CodeValueRole).toString().toUtf8().constData(),
    currentItem->text().toUtf8().constData() );
  vtkSmartPointer<vtkSlicerTerminologyType> region = vtkSmartPointer<vtkSlicerTerminologyType>::New();
  if (!logic->GetRegionInAnatomicContext(
    d->CurrentAnatomicContextName.toUtf8().constData(),
    regionId, region) )
    {
    qCritical() << Q_FUNC_INFO << ": Failed to find region '" << currentItem->text();
    return;
    }

  // Set current region
  this->setCurrentRegion(region);

  // Generate name based on selection if not custom
  if (d->NameAutoGenerated)
    {
    d->setNameFromCurrentTerminology();
    }
}

//-----------------------------------------------------------------------------
bool qSlicerTerminologyNavigatorWidget::setCurrentRegionModifier(vtkSlicerTerminologyType* modifier)
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  if (!modifier)
    {
    d->resetCurrentRegionModifier();
    qCritical() << Q_FUNC_INFO << ": Invalid region modifier object set";
    return false;
    }

  // Set current type modifier
  d->CurrentRegionModifierObject->Copy(modifier);

  // Select modifier if found
  int modifierIndex = d->findComboBoxIndexForModifier(d->ComboBox_AnatomicRegionModifier, modifier);
  if (modifierIndex != -1)
    {
    d->ComboBox_AnatomicRegionModifier->blockSignals(true);
    d->ComboBox_AnatomicRegionModifier->setCurrentIndex(modifierIndex);
    d->ComboBox_AnatomicRegionModifier->blockSignals(false);
    }
  return (modifierIndex != -1);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onRegionModifierSelectionChanged(int index)
{
  Q_UNUSED(index);
  Q_D(qSlicerTerminologyNavigatorWidget);

  vtkSmartPointer<vtkSlicerTerminologyType> modifier = vtkSmartPointer<vtkSlicerTerminologyType>::New();
  if (index < 0)
    {
    // If new index is invalid (happens on clearing the combobox), then set empty modifier
    this->setCurrentRegionModifier(modifier);
    return;
    }

  // Get current modifier object
  vtkSlicerTerminologiesModuleLogic* logic = d->terminologyLogic();
  if (!logic)
    {
    qCritical() << Q_FUNC_INFO << ": Failed to access terminology logic";
    return;
    }
  QMap<QString, QVariant> userData = d->ComboBox_AnatomicRegionModifier->itemData(index).toMap();
  vtkSlicerTerminologiesModuleLogic::CodeIdentifier modifierId(
    userData[QString::number(CodingSchemeDesignatorRole)].toString().toUtf8().constData(),
    userData[QString::number(CodeValueRole)].toString().toUtf8().constData(),
    d->ComboBox_AnatomicRegionModifier->itemText(index).toUtf8().constData() );
  if (!logic->GetRegionModifierInAnatomicRegion(
    d->CurrentAnatomicContextName.toUtf8().constData(),
    vtkSlicerTerminologiesModuleLogic::CodeIdentifierFromTerminologyType(d->CurrentRegionObject),
    modifierId, modifier) )
    {
    qCritical() << Q_FUNC_INFO << ": Failed to find modifier '" << d->ComboBox_AnatomicRegionModifier->itemText(index);
    return;
    }

  // Set current region modifier
  this->setCurrentRegionModifier(modifier);

  // Generate name based on selection if not custom
  if (d->NameAutoGenerated)
    {
    d->setNameFromCurrentTerminology();
    }
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onRegionSearchTextChanged(QString search)
{
  Q_UNUSED(search);

  this->populateRegionTable();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::onLogicModified()
{
  Q_D(qSlicerTerminologyNavigatorWidget);

  this->populateTerminologyComboBox();
  d->resetCurrentCategory();

  this->populateAnatomicContextComboBox();
  d->resetCurrentRegion();
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::anatomicalRegionExpandButtonUp()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->AnatomicalRegionExpandButton->setDown(false);
}

//-----------------------------------------------------------------------------
void qSlicerTerminologyNavigatorWidget::anatomicalRegionExpandButtonDown()
{
  Q_D(qSlicerTerminologyNavigatorWidget);
  d->AnatomicalRegionExpandButton->setDown(true);
}
